#include "CmdLineParser.h"

namespace jmlib {


	CmdLineOptionDefinition& CmdLineParser::addOptionDefinition(
		const std::string& name, 
		CmdLineOptionDefinition::ValuePresence values /*= CmdLineOptionDefinition::NONE*/) {
			args_.push_back(CmdLineOption(name, values));
			return args_.back().definition;
	}

	CmdLineOption* CmdLineParser::getOption(const std::string& name) {
		for (size_t i = 0; i < args_.size(); ++i) {
			if (args_[i].definition == name) {
				return &args_[i];
			}
		}
		throw CmdLineOptionNotFoundException(name);
	}

	CmdLineOptionInstance* CmdLineParser::operator[](const std::string& name) {
		return &getOption(name)->instance;
	}

	bool CmdLineParser::isOption(const std::string& arg) const {
		if (arg.size() < 2) {
			return false;
		}
		else if (arg.size() == 2) {
			return arg[0] == '-' && arg[1] != '-';
		}
		else {
			return (arg[0] == '-' && arg[1] == '-' && arg[2] != '-') ||
				(arg[0] == '-' && arg[1] != '-');
		}
	}

	std::string CmdLineParser::normalizeOption(const std::string& name) {		
		if (!isOption(name)) {
			return name;
		}
		int pos = 0;
		if (name.size() == 2) {
			pos = 1;
		}
		else  if (name[1] == '-') {
			pos = 2;
		}
		return name.substr(pos);
	}

	void CmdLineParser::addOptionValueOrIsolatedArg(CmdLineOption* opt, 
		const char* value) {
			if (opt == nullptr) {
				addIsolatedArg(value);
			}
			else if (opt->definition.getValueFilter()(value)) {
				opt->instance.getValues()->push_back(value);
			}
			else {
				addIsolatedArg(value);
			}
	}

	void CmdLineParser::parseCmdLine(int argc, char *argv[]) {
		CmdLineOption* lastOpt = nullptr;
		for (int i = 1; i  < argc; ++i) {
			if (isOption(argv[i])) {
				if (lastOpt) {
					// Kontrola hodnot za predoslim opsnom ale len pre pripad
					// ze su vyzadovane inak je to jedno.
					switch (lastOpt->definition.getValuePresence()) {
					case CmdLineOptionDefinition::ONE_REQUIRED:								
					case CmdLineOptionDefinition::MULTI_REQUIRED:

						if (lastOpt->instance.getValues()->empty()) {
							throw CmdLineOptionMissingValueException(
								lastOpt->instance.getUsedOptionName());
						}

						break;
					}
				}
				lastOpt = getOption(normalizeOption(argv[i]));
				lastOpt->instance.setUsed(argv[i]);
			}
			else {
				if (lastOpt) {
					// nie je opson potom moze byt hodnota predosleho opsnu
					switch (lastOpt->definition.getValuePresence()) {
					case CmdLineOptionDefinition::NONE:
						addIsolatedArg(argv[i]);
						break;
					case CmdLineOptionDefinition::ONE_OPTIONAL:
					case CmdLineOptionDefinition::ONE_REQUIRED:
						if (lastOpt->instance.getValues()->empty()) {
							addOptionValueOrIsolatedArg(lastOpt, argv[i]);
						}
						else {
							// Uz hodnotu za opsnom mam -> sa jedna o 
							// izolovany argument.
							addIsolatedArg(argv[i]);
						}
						break;
					case CmdLineOptionDefinition::MULTI_OPTIONAL:
					case CmdLineOptionDefinition::MULTI_REQUIRED:
						addOptionValueOrIsolatedArg(lastOpt, argv[i]);
						break;
					}
				}
				else {
					// nie je predosly opson -> len izoolovany argument
					addIsolatedArg(argv[i]);
				}
			}
		}
		// osetrenie vyzadovany hodnot posledneho opsnu
		if (lastOpt != nullptr) {
			if (lastOpt->instance.getValues()->empty()) {
				switch (lastOpt->definition.getValuePresence()) {
					case CmdLineOptionDefinition::ONE_REQUIRED:
					case CmdLineOptionDefinition::MULTI_REQUIRED:
						throw CmdLineOptionMissingValueException(
							lastOpt->instance.getUsedOptionName());
				}
			}
		}
	}

	void CmdLineParser::addIsolatedArg(const std::string& arg) {
		isolatedArgs_.push_back(arg);
	}

	void CmdLineParser::resetParser() {
		args_.clear();
		isolatedArgs_.clear();
	}

} // namespace jmlib
